#include "measure.hpp"
#include <climits>
#include <ctime>
#include <cfloat>
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <math.h>
#include <omp.h>

using namespace std;


Measure::Measure()
:minMeasure(0.002), repetitions(3), pOut(0), pDebug(0)
{
}

void Measure::setOutstream(std::ostream& out)
{
	if(isOutputOwnFile == true && this->pOut != 0)
	{
		((fstream*)this->pOut)->close();
		delete this->pOut;
	}
	this->pOut = &out;
}

void Measure::setOutstream(const char *pszFilename)
{
	fstream* pFile = new fstream();
	pFile->open(pszFilename, ios::out | ios::trunc);
	isOutputOwnFile = true;
	this->setOutstream(*pFile);
}

void Measure::setDebugOut(std::ostream& out)
{
	this->pDebug = &out;
}

void Measure::addFunction(const char *pszName, MeasureFunc_t func, MeasureFunc_t init, MeasureFunc_t cleanup, ModelFunc_t model)
{
	this->measureFuncs.push_back(MeasureFuncCollection(string(pszName), init, func, cleanup, model));
}

void Measure::addNewMeasureInterval(int start, int step, int stop)
{
	MeasureInterval i;
	i.start = start;
	i.step = step;
	i.stop = stop;
	this->intervals.push_back(i);
}
void Measure::addNewMeasure(int problemsize)
{
	MeasureInterval i;
	i.start = problemsize;
	i.step = 1;
	i.stop = problemsize;
	this->intervals.push_back(i);
}

void Measure::setMinMeasure(double min)
{
	this->minMeasure = min;
}

void Measure::setRepetitions(int rep)
{
	this->repetitions = rep;
}

void Measure::run()
{
	const int noOfMethods = this->measureFuncs.size();
	vectorMeasure_t::iterator iterMeasureFuncs = this->measureFuncs.begin();
	*pOut << "Problemgroesse";
	while(iterMeasureFuncs != this->measureFuncs.end())
	{
		*pOut << " " << iterMeasureFuncs->name;
		iterMeasureFuncs++;
	}
	vector<MeasureInterval>::iterator iterIntervals = this->intervals.begin();
	while(iterIntervals != this->intervals.end())
	{
		const int start  = (*iterIntervals).start,
				  step = (*iterIntervals).step,
				  stop  = (*iterIntervals).stop;
		for(int n=start ; n<=stop ; n+=step)
		{
			*pOut << endl << n;
			*pDebug << endl << n;
			iterMeasureFuncs = this->measureFuncs.begin();
			while(iterMeasureFuncs != this->measureFuncs.end())
			{
				double prediction = iterMeasureFuncs->model==0?9999.9:iterMeasureFuncs->model(n);
				int iterations = min(1000, max(1, (int)ceil(this->minMeasure/prediction)));

				void* pData;

				double time = (double)FLT_MAX;
				double diter = (double)iterations;
				void** pDatas = new void*[(int)this->repetitions];
				for(int rep = 0; rep < this->repetitions; rep++)
				{
					for(int its=0; its < iterations; its++)
					{
						iterMeasureFuncs->init(n, &pDatas[its]);
					}
					double start = omp_get_wtime();
					for(int its=0; its < iterations; its++)
					{
						iterMeasureFuncs->func(n, &pDatas[its]);
					}
					double stop = omp_get_wtime();
					for(int its=0; its < iterations; its++)
					{
						iterMeasureFuncs->cleanup(n, &pDatas[its]);
					}
					double t = (stop-start)/iterations;
					time = min(time, t);
				}
				delete [] pDatas;

				*pOut << " " << time;

				*pDebug << " " << fixed << (time*1000.0) << "ms (i:" << iterations << ") ";

				iterMeasureFuncs++;
			}

		}
		iterIntervals++;
	}

	if(isOutputOwnFile == true && this->pOut != 0)
	{
		((fstream*)this->pOut)->close();
		delete this->pOut;
	}
	cout << endl << "Done with the Measuring" << endl;//, press Enter to Exit" << endl;
}
//
//double measure(const int iter, MatrixMultiply& mm, MatrixMultiplyFunc func, const int threads = 1, const int maxRep = 2);
//const int iterFromModel(const double time);
//
//int main(int argc, const char* argv[])
//{
//	int maxCount = 2000;
//	int step = 1;
//
//	const int NO_OF_METHODS = 4;
//	MatrixMultiplyFunc func[NO_OF_METHODS];
//	func[0] = &MatrixMultiply::cblas_myDGEMM;
//	func[1] = &MatrixMultiply::method1;
//	func[2] = &MatrixMultiply::method2;
//	func[3] = &MatrixMultiply::method3;
//
//	const int NO_OF_THREAD_TESTS = 4;
//	int threads[NO_OF_THREAD_TESTS];
//	threads[0] = 1;
//	threads[1] = 2;
//	threads[2] = 3;
//	threads[3] = 4;
//
//	const char *fileName = "zeitmessung.txt";
//	if(argc >= 2)
//	{
//		fileName = argv[1];
//	}
//	cout << "writing " << fileName << endl;
//	cout.precision(5);
//	fstream myfile;
//	myfile.open(fileName, ios::out | ios::trunc);
//	//FileHead
//	myfile << "Problemgroesse dgemm Method1_1thread Method1_2thread Method1_3thread Method1_4thread Method2_1thread Method2_2thread Method2_3thread Method2_4thread Method3_1thread Method3_2thread Method3_3thread Method3_4thread";
//
//	int iter[NO_OF_METHODS];
//	double time;
//	for(int n=96; n <= maxCount; n+=step)
//	{
//		int ld = (n%2) ? n : n+1;
//		myfile << endl << n << " ";
//		cout << endl << n << "(ld:" << ld << ")" << " ";
//		iter[0] = iterFromModel(dgemm_model(n));
//		iter[1] = iterFromModel(method1_model(n));
//		iter[2] = iterFromModel(method2_model(n));
//		iter[3] = iterFromModel(method3_model(n));
//
//		for(int i=0; i<sizeof(iter)/sizeof(iter[0]); ++i)
//		{
//			for(int t=0; t<(i==0?1:sizeof(threads)/sizeof(threads[0])); ++t)
//			{
//				Matrix matA(n, ld);
//				Matrix matB(n, ld);
//				Matrix matC(n, ld, true);
//				MatrixMultiply mm(&matA, &matB, &matC);
//				time = measure(iter[i], mm, func[i], threads[t]);
//				myfile << time << " ";
//				cout << fixed << time << "(i:" << iter[i] << ") ";
//				//cout << endl << matC;
//			}
//		}
//	}
//	myfile.close();
//	cout << endl << "Done with the Measuring" << endl;//, press Enter to Exit" << endl;
//	//cin.get();
//	return 0;
//}
//
//double measure(const int iter, MatrixMultiply& mm, MatrixMultiplyFunc func, const int threads, const int maxRep)
//{
//	double time = (double)FLT_MAX;
//	double diter = (double)iter; // /threads
//	for(int rep = 0; rep < maxRep; rep++)
//	{
//		double start = omp_get_wtime();
//		for(int its=0; its < iter; its++)
//		{
//			(mm.*func)(threads);
//		}
//		double stop = omp_get_wtime();
//		double t = (stop-start)/iter;
//		time = min(time, t);
//	}
//	return time;
//}
//
//const int iterFromModel(const double time)
//{
//	const double minTime = 0.02;
//	return min(1000, max(1, (int)ceil(minTime/time)));
//}
